package SQL.abysnSQL;

import java.util.HashMap;

import com.sun.jndi.url.iiopname.iiopnameURLContextFactory;

import exceptions.DBTypeNotMatchException;

import Common.Types;
import heap.ResultRow;
import heap.ResultSchema;
import SQL.plan.Plan;
import SQL.planner.Planner;
import SQL.query.SQL;
import SQL.query.Select;
import SQL.scan.Scan;
import Transaction.Transaction;

/**
 * the nonterminal Value (have int/float/string subclass~)
 * 
 * @author Azure
 * 
 */
public class Value {
	public Value value;
	public ColName colname;
	public ConstValue constvalue;
	public ConstValue finalValue;
	public Value l, r;
	public Aop aop;
	public SQL subquery;
	public TYPE type;

	public static enum TYPE {
		SUBQUERY, COLNAME, VALUE, CONST, AOPEXPR
	}

	public int valueType = -1;

	public static final int INTEGER = 1;
	public static final int FLOAT = 2;
	public static final int STRING = 0;
	public static final int COLUMN = -1;

	/**
	 * Constructors.. value ::= (value) | col-name | const-value | value op
	 * value | (subquery with only one elements (or NULL) in the result table)
	 * 
	 * @Azure
	 */
	public Value(Value v) {
		value = v;
		type = TYPE.VALUE;
		valueType = v.valueType;
	}

	public Value(ColName cn) {
		colname = cn;
		type = TYPE.COLNAME;
		valueType = COLUMN;
	}

	public Value(ConstValue cv) {
		constvalue = cv;
		if (constvalue instanceof IntegerValue) {
			valueType = INTEGER;
		} else if (constvalue instanceof FloatValue) {
			valueType = FLOAT;
		} else {
			valueType = STRING;
		}
		type = TYPE.CONST;
	}

	public Value(Value l, Aop op, Value r) {
		this.l = l;
		this.aop = op;
		this.r = r;
		type = TYPE.AOPEXPR;
		switch (l.valueType) {
		case INTEGER:
			valueType = INTEGER;
			break;
		case FLOAT:
			valueType = FLOAT;
			break;
		case STRING:
			valueType = STRING;
		}
	}

	public Value(SQL s) {
		subquery = s;
		type = TYPE.SUBQUERY;
	}

	/**
	 * calculate the final value of subquery~
	 * 
	 * @param db
	 *            the current database
	 * @param tx
	 * @throws Exception
	 */
	public void calculate(String db, Transaction tx) throws Exception {
		if (subquery == null) {
			return;
		}
		Plan p = Planner.createQueryPlan((Select) subquery, db, tx);
		Scan s = p.open();
		ResultRow result = s.next();
		ResultSchema schema = s.getSchema();
		switch (schema.get(0).type) {
		case Types.INT:
		case Types.BOOLEAN:
			finalValue = new IntegerValue(Integer.parseInt(result.get(0)));
			break;
		case Types.FLOAT:
			finalValue = new FloatValue(Float.parseFloat(result.get(0)));
			break;
		default:
			finalValue = new StringValue(result.get(0));
		}
	}

	/**
	 * return the type of value for verify
	 * 
	 * @param schema
	 * @param db
	 * @param tx
	 * @return int type (as same as Types~)
	 * @throws Exception
	 */
	public int getType(ResultSchema schema, String db, Transaction tx)
			throws Exception {
		switch (type) {
		case CONST:
			if (constvalue instanceof IntegerValue)
				return Types.INT;
			if (constvalue instanceof FloatValue)
				return Types.FLOAT;
			if (constvalue instanceof StringValue)
				return Types.CHAR;
		case VALUE:
			return value.getType(schema, db, tx);
		case AOPEXPR:
			int left = l.getType(schema, db, tx);
			int right = r.getType(schema, db, tx);
			// check any of the two value is float..~
			if (left == Types.FLOAT || right == Types.FLOAT)
				return Types.FLOAT;
			return left;
		case SUBQUERY:
			this.calculate(db, tx);
			if (finalValue instanceof IntegerValue)
				return Types.INT;
			if (finalValue instanceof FloatValue)
				return Types.FLOAT;
			if (finalValue instanceof StringValue)
				return Types.CHAR;
		case COLNAME:
			String name = colname.toString();
			for (int i = 0; i < schema.size(); i++) {
				if (schema.get(i).name.endsWith("." + name)
						|| schema.get(i).name.equals(name)) {
					return schema.get(i).type;
				}
			}
		}
		return 0;
	}

	/**
	 * return final value~
	 * 
	 * @param schema
	 * @param result
	 * @param pos
	 * @param db
	 * @param tx
	 * @return
	 * @throws Exception
	 */
	public ConstValue getFinalValue(ResultSchema schema, ResultRow result,
			String db, Transaction tx) throws Exception {
		switch (type) {
		case SUBQUERY: {
			this.calculate(db, tx);
			return finalValue;
		}
		case VALUE:
			return value.getFinalValue(schema, result, db, tx);
		case CONST:
			return constvalue;
		case AOPEXPR: {
			ConstValue left = l.getFinalValue(schema, result, db, tx);
			ConstValue right = r.getFinalValue(schema, result, db, tx);
			switch (aop.aop) {
			case PLUS: {
				if (left instanceof FloatValue || right instanceof FloatValue)
					return new FloatValue(left.floatValue()
							+ right.floatValue());
				else if (left instanceof IntegerValue
						&& right instanceof IntegerValue)
					return new IntegerValue(left.intValue() + right.intValue());
			}
			case MINUS: {
				if (left instanceof FloatValue || right instanceof FloatValue)
					return new FloatValue(left.floatValue()
							- right.floatValue());
				else if (left instanceof IntegerValue
						&& right instanceof IntegerValue)
					return new IntegerValue(left.intValue() - right.intValue());
			}
			case TIMES: {
				if (left instanceof FloatValue || right instanceof FloatValue)
					return new FloatValue(left.floatValue()
							* right.floatValue());
				else if (left instanceof IntegerValue
						&& right instanceof IntegerValue)
					return new IntegerValue(left.intValue() * right.intValue());
			}
			case DIV: {
				if (left instanceof FloatValue || right instanceof FloatValue)
					return new FloatValue(left.floatValue()
							/ right.floatValue());
				else if (left instanceof IntegerValue
						&& right instanceof IntegerValue)
					return new IntegerValue(left.intValue() / right.intValue());
			}
			case MOD: {
				if (left instanceof FloatValue || right instanceof FloatValue)
					return new FloatValue(left.floatValue()
							% right.floatValue());
				else if (left instanceof IntegerValue
						&& right instanceof IntegerValue)
					return new IntegerValue(left.intValue() % right.intValue());
			}
			}
		}
		case COLNAME:
			String name = colname.toString();
			int pos;
			for (pos = 0; pos < schema.size(); pos++) {
				if (schema.get(pos).name.endsWith("." + name)
						|| schema.get(pos).name.equals(name)) {
					break;
				}
			}
			switch (schema.get(pos).type) {
			case Types.INT:
			case Types.BOOLEAN:
				return new IntegerValue(Integer.parseInt(result.get(pos)));
			case Types.FLOAT:
				return new FloatValue(Float.parseFloat(result.get(pos)));
			default:
				return new StringValue(result.get(pos));
			}
		}

		return null;
	}

	/**
	 * utility for get information
	 */
	public int valueType() {
		return valueType;
	}

	public String toString() {
		switch (type) {
		case COLNAME:
			return colname.toString();
		case CONST:
			return constvalue.toString();
		case AOPEXPR:
			return l.toString() + aop.toString() + r.toString();
		case SUBQUERY:
			return subquery.toString();
		default:
			return value.toString();
		}
	}

	public ColName getColName() {
		switch (type) {
		case COLNAME:
			return colname;
		case VALUE:
			return value.getColName();
		}
		return null;
	}

	public ConstValue getConstValue() {
		switch (type) {
		case CONST:
			return constvalue;
		case VALUE:
			return value.constvalue;
		}
		return null;
	}

	/**
	 * intValue/floatValue/stringValue, calculate value & return certain value~~
	 * 
	 * @param tx
	 * @return
	 * @throws DBTypeNotMatchException
	 */
	public int intValue(Transaction tx) throws DBTypeNotMatchException {
		switch (type) {
		// SUBQUERY,COLNAME,VALUE,CONST,AOPEXPR
		case AOPEXPR:
			switch (aop.aop) {
			case PLUS:
				return l.intValue(tx) + r.intValue(tx);
			case MINUS:
				return l.intValue(tx) - r.intValue(tx);
			case TIMES:
				return l.intValue(tx) * r.intValue(tx);
			case DIV:
				return l.intValue(tx) / r.intValue(tx);
			case MOD:
				return l.intValue(tx) % r.intValue(tx);
			}
		case CONST:
			return constvalue.intValue();
		case VALUE:
			return value.intValue(tx);
		case COLNAME:
		case SUBQUERY:
			// TODO
		}
		throw new DBTypeNotMatchException(new Exception());
	}

	public float floatValue(Transaction tx) throws DBTypeNotMatchException {
		switch (type) {
		case AOPEXPR:
			switch (aop.aop) {
			case PLUS:
				return l.floatValue(tx) + r.floatValue(tx);
			case MINUS:
				return l.floatValue(tx) - r.floatValue(tx);
			case TIMES:
				return l.floatValue(tx) * r.floatValue(tx);
			case DIV:
				return l.floatValue(tx) / r.floatValue(tx);
			case MOD:
				return l.floatValue(tx) % r.floatValue(tx);
			}
		case CONST:
			return constvalue.floatValue();
		case VALUE:
			return value.floatValue(tx);
		case COLNAME:
		case SUBQUERY:
			// TODO
		}
		throw new DBTypeNotMatchException(new Exception());
	}

	public String stringValue(Transaction tx) throws DBTypeNotMatchException {
		switch (type) {
		case CONST:
			return constvalue.stringValue();
		case VALUE:
			return value.stringValue(tx);
		case AOPEXPR:
		case COLNAME:
		case SUBQUERY:
			// TODO
		}
		throw new DBTypeNotMatchException(new Exception());
	}

	/**
	 * @param attrType
	 * @param tx
	 * @return
	 * @throws DBTypeNotMatchException
	 */
	public String getString(int attrType, Transaction tx)
			throws DBTypeNotMatchException {
		switch (attrType) {
		case Types.INT:
		case Types.BOOLEAN:
			return intValue(tx) + "";
		case Types.FLOAT:
			return floatValue(tx) + "";
		case Types.TIMESTAMP:
		case Types.VARCHAR:
		case Types.CHAR:
		case Types.DATETIME:
		case Types.DECIMAL:
			return stringValue(tx);
		}
		return null;
	}

	/**
	 * validate the type of Value
	 * 
	 * @param schema
	 * @return
	 */
	public boolean validation(ResultSchema schema) {
		switch (type) {
		case VALUE:
			return value.validation(schema);
		case COLNAME: {
			for (int i = 0; i < schema.size(); i++) {
				if (schema.get(i).name.endsWith("." + colname)
						|| schema.get(i).name.equals(colname)) {
					return true;
				}
			}
			return false;
		}
		case CONST:
			return true;
		case AOPEXPR:
		case SUBQUERY:
			return false;
		}
		return false;
	}
}
